#include "message_encode_buffer.h"

#include <assert.h>
#include <string>
#include "fecEncoderRS8.h"
#include "fecEncoderRS16.h"

MessageEncodeBuffer::MessageEncodeBuffer(int block_size, int parity_num)
    :fec_block_size_(block_size),
     fec_parity_num_(parity_num),
     next_msg_id(0),
     next_block_id(0)
{
    assert(block_size > 0);
    assert(parity_num > 0);
}

FecPacketList MessageEncodeBuffer::makeMessage(const char* data, int len)
{
    FecPacketList packets;
    int msg_id = getNextMsgId();

    if (len < UDP_EFC_PACKET_BODY_MAX_SIZE)
    {
        SpFecPacket packet = std::make_shared<FecPacket>();
        packet->set_msg_data(data, len);
        packet->set_msg_id(msg_id);
        packets.push_back(packet);
    }
    else
    {
        int num = (len + UDP_EFC_PACKET_BODY_MAX_SIZE - 1) / UDP_EFC_PACKET_BODY_MAX_SIZE;
        for (int i = 0; i < num; ++i)
        {
            SpFecPacket packet = std::make_shared<FecPacket>();

            const char* base_addr = data + i * UDP_EFC_PACKET_BODY_MAX_SIZE;
            int play_load_length = (i == num - 1) ? (len - i * UDP_EFC_PACKET_BODY_MAX_SIZE) : (UDP_EFC_PACKET_BODY_MAX_SIZE);
            packet->set_msg_data(base_addr, play_load_length);
            packet->set_msg_id(msg_id);
            packets.push_back(packet);
        }
    }

    return packets;
}

FecPacketList  MessageEncodeBuffer::encode()
{
    std::unique_ptr<uint8_t *[]> txPtr(new uint8_t *[getTotalSize()]);

    int index = 0;
    for (auto& packet : packets_)
        txPtr.get()[index++] = packet->get_fecpayload();
    assert(getBlockSize() == index);
    paritys_.clear();
    for (int i = 0; i < getParityNum(); ++i)
    {
        SpFecPacket paket = std::make_shared<FecPacket>();
        paritys_.push_back(paket);
        txPtr.get()[index++] = paket->get_fecpayload();
    }
    assert(getTotalSize() == index);

    Fec::FecEncoderRS16 encoder;
    bool ret = encoder.Init(getBlockSize(), getParityNum(), sizeof(UdpFecPacketMsg));
    assert(ret);

    if (!ret)
    {
        //fix me:
    }

    for (int i = 0; i < getBlockSize(); ++i)
    {
        encoder.Encode(
            i, (const char*)(txPtr.get()[i]), (char**)(&(txPtr.get()[getBlockSize()])), getBlockSize());
    }

    FecPacketList sendingmsgs = fillFecInfo();
    clear();

    return sendingmsgs;
}

FecPacketList  MessageEncodeBuffer::fillFecInfo()
{
    FecPacketList sendingmsgs;
    sendingmsgs.insert(sendingmsgs.end(), packets_.begin(), packets_.end());
    sendingmsgs.insert(sendingmsgs.end(), paritys_.begin(), paritys_.end());
    int block_id = getNextBlockId();
    int block_size = packets_.size();
    int parity_num = getParityNum();

    int index = 0;
    for (auto& packet : sendingmsgs)
    {
        packet->set_block_id(block_id);
        packet->set_block_size(block_size);
        packet->set_parity_num(parity_num);
        packet->set_segment_id(index++);
    }

    return sendingmsgs;
}

void MessageEncodeBuffer::clear()
{
    next_msg_id = 0;
    packets_.clear();
    paritys_.clear();
}

FecPacketList MessageEncodeBuffer::appendAndTryEncode(const char* buf, int len)
{
    FecPacketList sendingpks(0);
    FecPacketList pks = makeMessage(buf, len);
    packets_.insert(packets_.end(), pks.begin(), pks.end());

    if (!ready())
        return sendingpks;

    return encode();
}


bool MessageEncodeBuffer::ready()
{
    return packets_.size() >= fec_block_size_;
}

int MessageEncodeBuffer::getBlockSize()
{
    return packets_.size();
}

int MessageEncodeBuffer::getParityNum()
{
    int parity = packets_.size() * fec_parity_num_ / fec_block_size_;
    return  parity == 0 ? 1 : parity;
}
    
int MessageEncodeBuffer::getTotalSize()
{
    return getBlockSize() + getParityNum();
}

int MessageEncodeBuffer::getNextBlockId()
{
    return ++next_block_id;
}

int MessageEncodeBuffer::getNextMsgId()
{
    return next_msg_id++;
}

FecPacketList MessageEncodeBuffer::forceEncode()
{
    FecPacketList sendingmsgs;
    if (packets_.empty())
        return sendingmsgs;

    return encode();
}